package com.xpec.c4.h5net;

import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.Arrays;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.frame.FrameDecoder;
import org.json.JSONObject;

import com.xpec.c4.codec.CodecUtility;
import com.xpec.c4.codec.amf3.Amf3Codec;
import com.xpec.c4.game.utility.GameUtility;
import com.xpec.c4.message.ConstantCode;
import com.xpec.c4.message.MessageTypes;
import com.xpec.c4.service.message.NetMessage;

class H5XSENetNettyDecoder extends FrameDecoder {
	private static final Log log = LogFactory
			.getLog(H5XSENetNettyDecoder.class);

	private static final int MIN_PACKET_SIZE = 6;

	public static final int MAX_DATA_LENGTH = (1 << 16) - 1;

	public static final byte START_FLAG1 = (byte) (Short.MAX_VALUE >> 8);
	public static final byte START_FLAG2 = (byte) (Short.MAX_VALUE);

	static final MyThreadLocalByteArray decoderTempMem = new MyThreadLocalByteArray();

	// / 試試 codec 只有一份
	static final Amf3Codec codec = new Amf3Codec();

	// //////////////////////////////////////////////////////////////////

	public H5XSENetNettyDecoder() {
	}

	@Override
	protected Object decode(ChannelHandlerContext ctx, Channel channel,
			ChannelBuffer buffer) {

		if (buffer.readableBytes() < MIN_PACKET_SIZE)
			return null;

		// / 先訂一個標記, 之後才能將指標快速回到這個標記的地方
		buffer.markReaderIndex();
		// buffer.readerIndex(readerIndex);

		// / 我們的消息結構
		while (buffer.readableBytes() >= MIN_PACKET_SIZE) {
			byte magic1 = buffer.readByte();
			if (magic1 != START_FLAG1) {
				buffer.markReaderIndex();
				channel.close();
				buffer.readBytes(buffer.readableBytes());
				log.error("start flag 1 is error!");
				return null;
				// continue;
			}

			byte magic2 = buffer.readByte();
			if (magic2 != START_FLAG2) {
				buffer.markReaderIndex();
				channel.close();
				buffer.readBytes(buffer.readableBytes());
				log.error("start flag 2 is error!");
				return null;
				// continue;
			}

			int dataLength = ByteBuffer.wrap(buffer.copy(2, 4).array())
					.getInt();
			if (buffer.readableBytes() < dataLength) {
				buffer.resetReaderIndex();
				return null;
			}

			int messageTypeJSON = 0;
			String moduleID = "";
			byte[] messageContent = null;
			byte[] sendContent = null;

			JSONObject decodeString = new JSONObject();
			try {
				decodeString = new JSONObject(Charset.defaultCharset()
						.decode(buffer.toByteBuffer(6, (dataLength - 4)))   //toByteBuffer此函式並不會變更readindex & writeindex
						.toString());
				
				
				messageTypeJSON = H5Coder.getMessageTypeJSON(decodeString);
				moduleID = H5Coder.getModuleIdJSON(decodeString);
				
				if (messageTypeJSON == MessageTypes.C_SLOT_DM_DOSEAT)
				{
					H5XSENetSessionPool.getSession(channel).getParameters().put("halltype", ((JSONObject)decodeString.get("contents")).get("halltype"));
				}
				
				Object jsonToClass = 0;
				Class<?> realClass = MessageTypes.messageMap
						.get(messageTypeJSON).getRealClass();
				if (realClass != null && GameUtility.isHaveString(realClass.getName())) {
					jsonToClass = H5Coder.jsonStringToClass(decodeString
							.getJSONObject("contents").toString(), realClass);
				}
				
				// byte[] requestByte = request.toString().getBytes("UTF-8");
				messageContent = CodecUtility.contentEncode(jsonToClass,
						messageTypeJSON);
				sendContent = Arrays.copyOfRange(messageContent, 1,
						messageContent.length);
				
				buffer.readerIndex(buffer.writerIndex());
			} 
			catch (Exception e2) 
			{
				// TODO Auto-generated catch block
				e2.printStackTrace();
				if (log.isDebugEnabled()) 
				{
					log.debug("H5XSENetNettyDecoder recv message json decode Error!!  messagetype: "
							+ ConstantCode.getName(MessageTypes.class,messageTypeJSON) 
							+ " moduleId: " + moduleID + " error:"+e2.getMessage(),e2);
				}
			}

			if (
					messageTypeJSON != 0
					&& 
					messageTypeJSON != MessageTypes.C_PERIOD_SYNC_REQUEST) 
			{
				if (log.isDebugEnabled()) 
				{
					log.debug("H5XSENetNettyDecoder recv message: "
							+ ConstantCode.getName(MessageTypes.class,
									messageTypeJSON) + " moduleId: " + moduleID);
				}
			}

			// / 獲取每個 thread 唯一的一份 mem buffer
			// TODO 默认的buff size是65535，如果超了，就会报错
			ChannelBuffer buffer2 = ChannelBuffers.wrappedBuffer(sendContent);
			ByteBuffer memBuffer = decoderTempMem.getClearBuffer(dataLength);
			memBuffer.limit(sendContent.length); // / 設定只需要 copy 這麼多
			buffer2.readBytes(memBuffer);
			memBuffer.flip();

			NetMessage msg = new NetMessage();
			msg.setMessageType(messageTypeJSON);

			try {
				byte[] tmp = new byte[memBuffer.remaining()];
				memBuffer.get(tmp);
				msg.setModuleID(moduleID);
				msg.setMessageContentBytes(tmp);

			} 
			catch (Exception e) 
			{
				e.printStackTrace();
				if (log.isDebugEnabled()) 
				{
					log.debug("H5XSENetNettyDecoder NetMessage Error!!  messagetype: "
							+ ConstantCode.getName(MessageTypes.class,messageTypeJSON) 
							+ " moduleId: " + moduleID + " error:"+e.getMessage(),e);
				}
			}

			H5XSEPacketCategoryMeter.getInstance().recordInput(messageTypeJSON,
					msg.getMessageContentBytes().length);
			
			return msg;
		}

		buffer.resetReaderIndex();
		return null;
	}

	// //////////////////////////////////////////////////////////////////
	// / 預先替每個 thread 都準備一個 byte[], 免得一直在 allocate 這個 byte[]

	static class MyByteBuffer {
		public byte[] byteArray = null;
		public ByteBuffer byteBuffer = null;
	}

	static class MyThreadLocalByteArray extends ThreadLocal<MyByteBuffer> {
		private int size = H5XSENetNettyDecoder.MAX_DATA_LENGTH;

		public MyThreadLocalByteArray() {
		}

		public MyThreadLocalByteArray(int bufferSize) {
			size = bufferSize;
		}

		@Override
		protected synchronized MyByteBuffer initialValue() {
			MyByteBuffer buffer = new MyByteBuffer();
			buffer.byteArray = new byte[size];
			buffer.byteBuffer = ByteBuffer.allocate(size);
			return buffer;
		}

		public ByteBuffer getClearBuffer() {
			MyByteBuffer buffer = super.get();
			buffer.byteBuffer.clear();
			return buffer.byteBuffer;
		}

		public ByteBuffer getClearBuffer(int size) {
			MyByteBuffer buffer = super.get();
			ByteBuffer bb = buffer.byteBuffer;
			if (bb.capacity() < size) {
				return ByteBuffer.allocate(size);
			}
			bb.clear();
			return bb;
		}

		public byte[] getClearByteArray() {
			MyByteBuffer buffer = super.get();
			Arrays.fill(buffer.byteArray, (byte) 0);
			return buffer.byteArray;
		}
	}
}